/*
 * Created on May 29, 2006 4:23:01 PM
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.aelitis.azureus.ui.skin;

import java.util.Collections;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.gudy.azureus2.core3.internat.IntegratedResourceBundle;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;

/**
 * Implementation of SkinProperties using a IntegratedResourceBundle loaded from hard coded paths.
 * <P>
 * Three level lookup of keys:
 * <li>(plugin) skin property file
 * <li>defaults property file
 * <li>Azureus MessageText class <br>
 * Additionally, checks each for platform specific keys.
 * <p>
 * <br>
 * Values containing "{*}" are replaced with a lookup of *
 * 
 * @author TuxPaper
 * @created May 29, 2006
 * 
 */
public class SkinPropertiesImpl implements SkinProperties {
    private static final LogIDs LOGID = LogIDs.UI3;

    public static final String PATH_SKIN_DEFS = "com/aelitis/azureus/ui/skin/";

    private static final String FILE_SKIN_DEFS = "skin3.properties";

    private static final Pattern PAT_PARAM_ALPHA = Pattern.compile("\\{([^0-9].+?)\\}");

    private static final Pattern PAT_PARAM_NUM = Pattern.compile("\\{([0-9]+?)\\}");

    private IntegratedResourceBundle rb;

    private final ClassLoader classLoader;

    public SkinPropertiesImpl() {
        this(SkinPropertiesImpl.class.getClassLoader(), PATH_SKIN_DEFS, FILE_SKIN_DEFS);
    }

    public SkinPropertiesImpl(ClassLoader classLoader, String skinPath, String mainSkinFile) {
        this.classLoader = classLoader;
        skinPath = skinPath.replaceAll("/", ".");
        if (!skinPath.endsWith(".")) {
            skinPath += ".";
        }
        if (mainSkinFile.endsWith(".properties")) {
            mainSkinFile = mainSkinFile.substring(0, mainSkinFile.length() - 11);
        }
        ResourceBundle bundle = ResourceBundle.getBundle(skinPath + mainSkinFile, Locale.getDefault(), classLoader);
        rb = new IntegratedResourceBundle(bundle, Collections.EMPTY_MAP, 1200);
        rb.setUseNullList(true);

        String sFiles = rb.getString("skin.include", null);
        if (sFiles != null) {
            String[] sFilesArray = sFiles.split(",");
            for (int i = 0; i < sFilesArray.length; i++) {
                String sFile = (sFilesArray[i].startsWith("/") ? sFilesArray[i].substring(1) : skinPath + sFilesArray[i]);
                sFile = sFile.replaceAll("/", ".");
                try {
                    ResourceBundle subBundle = ResourceBundle.getBundle(sFile, Locale.getDefault(), classLoader);
                    rb.addResourceMessages(subBundle);
                } catch (Throwable t) {
                    Debug.out("Err loading skin include: " + sFile, t);
                }
            }
        }
    }

    public void addResourceBundle(ResourceBundle subBundle, String skinPath) {
        addResourceBundle(subBundle, skinPath, classLoader);
    }

    public void addResourceBundle(ResourceBundle subBundle, String skinPath, ClassLoader loader) {
        try {
            clearCache();
            rb.addResourceMessages(subBundle);

            try {
                String sFiles = subBundle.getString("skin.include");

                if (sFiles != null && skinPath != null) {

                    String[] sFilesArray = Constants.PAT_SPLIT_COMMA.split(sFiles);
                    for (int i = 0; i < sFilesArray.length; i++) {
                        String sFile = (sFilesArray[i].startsWith("/") ? sFilesArray[i].substring(1) : skinPath + sFilesArray[i]);
                        sFile = sFile.replaceAll("/", ".");
                        try {
                            ResourceBundle incBundle = ResourceBundle.getBundle(sFile, Locale.getDefault(), loader);
                            rb.addResourceMessages(incBundle);
                        } catch (Throwable t) {
                            Debug.out("Err loading skin include: " + sFile, t);
                        }
                    }
                }
            } catch (MissingResourceException e) {
                // get this if skin.include not defined, which is entirely possible
            }
        } catch (Throwable t) {
            Debug.out("Err loading skin include: " + subBundle, t);
        }
    }

    // public Properties getProperties() {
    // return properties;
    // }

    public void addProperty(String name, String value) {
        rb.addString(name, value);
    }

    public boolean hasKey(String name) {
        if (name == null) {
            return false;
        }

        String osName = null;
        if (Constants.isWindows) {
            osName = name + "._windows";
        } else if (Constants.isOSX) {
            osName = name + "._mac";
        } else if (Constants.isUnix) {
            osName = name + "._unix";
        } else if (Constants.isFreeBSD) {
            osName = name + "._freebsd";
        } else if (Constants.isLinux) {
            osName = name + "._linux";
        } else if (Constants.isSolaris) {
            osName = name + "._solaris";
        }

        boolean contains = false;
        if (osName != null) {
            // can't use containsKey on IntegratedResourceBundle :(
            contains = rb.getString(osName, null) != null;
        }

        if (!contains) {
            contains = rb.getString(name, null) != null;
        }
        return contains;
    }

    public String getReferenceID(String name) {
        String value = getValue(name, null, false);
        if (value == null || value.length() < 2) {
            return null;
        }
        if (value.charAt(0) == '{' && value.charAt(value.length() - 1) == '}') {
            return value.substring(1, value.length() - 1);
        }
        return null;
    }

    private String getValue(String name, String[] params) {
        return getValue(name, params, true);
    }

    private String getValue(String name, String[] params, boolean expandReferences) {
        String value = null;
        String osName = null;

        if (name == null) {
            return null;
        }

        if (Constants.isWindows) {
            osName = name + "._windows";
        } else if (Constants.isOSX) {
            osName = name + "._mac";
        } else if (Constants.isUnix) {
            osName = name + "._unix";
        } else if (Constants.isFreeBSD) {
            osName = name + "._freebsd";
        } else if (Constants.isLinux) {
            osName = name + "._linux";
        } else if (Constants.isSolaris) {
            osName = name + "._solaris";
        }

        if (osName != null) {
            value = rb.getString(osName, null);
        }

        if (value == null) {
            value = rb.getString(name, null);
        }

        if (expandReferences && value != null && value.indexOf('}') > 0) {
            Matcher matcher;

            if (params != null) {
                matcher = PAT_PARAM_NUM.matcher(value);
                while (matcher.find()) {
                    String key = matcher.group(1);
                    try {
                        int i = Integer.parseInt(key);

                        if (i < params.length) {
                            value = value.replaceAll("\\Q{" + key + "}\\E", params[i]);
                        } else {
                            value = value.replaceAll("\\Q{" + key + "}\\E", "");
                        }
                    } catch (Exception e) {
                    }
                }
            }

            matcher = PAT_PARAM_ALPHA.matcher(value);
            while (matcher.find()) {
                String key = matcher.group(1);
                String text = getValue(key, params);
                if (text == null) {
                    text = MessageText.getString(key);
                }
                value = value.replaceAll("\\Q{" + key + "}\\E", text);
            }
        }

        return value;
    }

    public int getIntValue(String name, int def) {
        String value = getValue(name, null);
        if (value == null) {
            return def;
        }

        int result = def;
        try {
            result = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // ignore error.. it might be valid to store a non-numeric..
            // e.printStackTrace();
        }
        return result;
    }

    public int[] getColorValue(String name) {
        int[] colors = new int[4];
        String value = getValue(name, null);

        if (value == null || value.length() == 0 || value.startsWith("COLOR_")) {
            colors[0] = colors[1] = colors[2] = -1;
            return colors;
        }

        try {
            if (value.charAt(0) == '#') {
                // hex color string
                long l = Long.parseLong(value.substring(1), 16);
                if (value.length() == 9) {
                    colors = new int[] { (int) ((l >> 24) & 255), (int) ((l >> 16) & 255), (int) ((l >> 8) & 255), (int) (l & 255) };
                } else {
                    colors[0] = (int) ((l >> 16) & 255);
                    colors[1] = (int) ((l >> 8) & 255);
                    colors[2] = (int) (l & 255);
                    colors[3] = 255;
                }
            } else {
                StringTokenizer st = new StringTokenizer(value, ",");
                colors[0] = Integer.parseInt(st.nextToken());
                colors[1] = Integer.parseInt(st.nextToken());
                colors[2] = Integer.parseInt(st.nextToken());
                colors[3] = st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 255;
            }
        } catch (Exception e) {
            // e.printStackTrace();
            colors[0] = colors[1] = colors[2] = -1;
        }

        return colors;
    }

    public String getStringValue(String name) {
        return getStringValue(name, (String[]) null);
    }

    public String getStringValue(String name, String def) {
        return getStringValue(name, (String[]) null, def);
    }

    public String[] getStringArray(String name) {
        return getStringArray(name, (String[]) null);
    }

    public String[] getStringArray(String name, String[] params) {
        String s = getValue(name, params);
        if (s == null) {
            return null;
        }

        String[] values = Constants.PAT_SPLIT_COMMAWORDS.split(s);
        if (values == null) {
            return new String[] { s };
        }

        return values;
    }

    public String getStringValue(String name, String[] params) {
        return getValue(name, params);
    }

    public String getStringValue(String name, String[] params, String def) {
        String s = getValue(name, params);
        return (s == null) ? def : s;
    }

    // @see com.aelitis.azureus.ui.skin.SkinProperties#getBooleanValue(java.lang.String, boolean)
    public boolean getBooleanValue(String name, boolean def) {
        String s = getStringValue(name, (String) null);
        if (s == null) {
            return def;
        }
        return s.toLowerCase().equals("true") || s.equals("1");
    }

    public void clearCache() {
        rb.clearUsedMessagesMap(1);
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }
}
